Android 平台 Native Crash 问题分析与定位

您所在的位置:网站首页 react native 安卓5中崩溃 Android 平台 Native Crash 问题分析与定位

Android 平台 Native Crash 问题分析与定位

2024-01-14 12:58| 来源: 网络整理| 查看: 265

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一 Native Crash 简介

Native Crash 是发生在 Android 系统中 C/C++ 层面的 Crash,具体可参考: # Android 平台 Native Crash 捕获原理详解

二 Native C/C++ Libraries 简介

Android 开发中通常是将 Native 层代码打包为.so格式的动态库文件,然后供 Java 层调用,.so库文件通常有以下三种来源:

Android 系统自带的核心组件和服务,如多媒体库、OpenGL ES 图形库等 引入的第三方库 开发者自行编译生成的动态库 2.1 .so文件组成

一个完整的 .so 文件由 C/C++代码和一些 debug 信息组成,这些 debug 信息会记录 .so中所有方法的对照表,就是方法名和其偏移地址的对应表,也叫做符号表 symbolic 信息,这种 .so被称为未 strip 的,通常体积会比较大。

2023-03-01-image-3.png

通常 release 的.so都是需要经过 strip 操作,strip 之后的.so中的 debug 信息会被剥离,整个 so 的体积也会缩小许多。

可以简单将这个 debug 信息理解为 Java 代码混淆中的 mapping 文件,只有拥有这个 mapping 文件才能进行堆栈分析。如果堆栈信息丢了,基本上堆栈无法还原,问题也无法解决。

所以,这些 debug 信息尤为重要,是我们分析 Native Crash 问题的关键信息,那么我们在编译 .so 时 候务必保留一份未被 strip 的.so或者剥离后的符号表信息,以供后面问题分析。

2.2 查看 so 状态

也可以通过命令行来查看.so的状态,Linux 下使用 file 命令即可,在命令返回值里面可以查看到.so的一 些基本信息。

如下代码所示,stripped 代表是没有 debug 信息的.so,with debug_info, not stripped 代表携带 debug 信息的.so。

file libbreakpad-core-s.so libbreakpad-core-s.so: *******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, stripped file libbreakpad-core.so libbreakpad-core.so: ******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, with debug_info, not stripped 2.3 获取 strip 和未被 strip 的 so

目前 Android Studio 无论是使用 mk 或者 Cmake 编译的方式都会同时输出 strip 和未 strip 的 so,如下图是 Cmake 编译 so 产生的两个对应的 so。

2023-03-01-image -1--4.png

2023-03-01-image -2--3.png

strip 之前的 so 路径:{project}/app/build/intermediates/merged_native_libs

strip 之后的 so 路径:{project}/app/build/intermediates/stripped_native_libs

三 Native Crash 捕获与解析 3.1 通过 DropBox 日志解析

Android Dropbox 是 Android 在 Froyo(API level 8) 引入的用来持续化存储系统数据的机制。主要用于记录 Android 运行过程中, 内核, 系统进程, 用户进程等出现严重问题时的 log, 可以认为这是一个可持续存储的系统级别的 logcat。

相关文件记录存储目录:/data/system/dropbox

只需要将 DropBox 的日志获取到即可进行分析解决,下面贴上一份 Log 示例。

2023-03-01-image -3--3.png

DropBox 中的 Tombstone 文件显示,Native Crash 发生在动态库 libnativedemo.so 中,具体的方法和行数可以用 Android/SDK/NDK 提供的工具 linux-android-addr2line 来进一步定位。

addr2line 工具通常在 ndk 目录下,例如:

${SDK Path}/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line

然后使用命令行,既可将偏移地址转换为 crash 方法和行数

arm-linux-androideabi-addr2line [option(s)] [addr(s)]

简单来说就是 arm-linux-androideabi-addr2line + 可选项 + 异常地址

[option(s)]介绍@从文件中读取 options-a在结果中显示地址 addr-b设置二进制文件的格式-e设置输入文件(常用:选项后面需要跟报错的共享库,用于 addr2line 程序分析)-iunwind inline function-jRead section-relative offsets instead of addresses-p让输出更易读-s在输出中,剥离文件夹名称-f显示函数名称-C(大写的) 将输出的函数名 demangle-h输出帮助-v输出版本信息

使用 addr2line 进行解析,结果可以看到,Native Crash 发生在文件 native-lib.cpp 的 17 行的 Crash() 方法中

2023-03-01-image -4--3.png

结合代码分析,在 Crash() 中,对空指针 *a 进行了赋值操作,所以造成了 crash。

#include #include extern "C" JNIEXPORT jstring JNICALL Java_com_elijah_nativedemo_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } /** * 引起 crash */ void Crash() { volatile int *a = (int *) (NULL); *a = 1; } extern "C" JNIEXPORT jstring JNICALL Java_com_elijah_nativedemo_MainActivity_nativeCrash(JNIEnv *env, jobject thiz) { Crash(); }

示例代码:github.com/elijah398/N…

通过读取 DropBox 获得 crash log -> addr2line 解析偏移地址的方法确实可以定位到 native crash 发生的现场,但是 DropBox 只有系统应用能访问,非系统应用拿不到日志。对于非系统应用,可以使用 google 提供的开源工具 BreakPad 进行监测分析。

3.2 通过 BreakPad 捕获解析 3.2.1 breakpad 简介

BreakPad 是 Google 开发的一个跨平台 C/C++ dump捕获开源库,崩溃文件使用微软的 minidump格式存储,也支持发送这个 dump 文件到你的服务器,breakpad 可以在程序崩溃时触发 dump 写入操作,也可以在没有触发 dump 时主动写 dump 文件。breakpad 支持 windows、linux、macos、android、ios 等。目前已有 Google Chrome, Firefox, Google Picasa, Camino, Google Earth 等项目使用。

3.2.2 实现原理

在不同平台下使用平台特有的函数以及方式实现异常捕获:

Windows:通过 SetUnhandledExceptionFilter()设置崩溃回掉函数

Max OS:监听 Mach Exception Port 获取崩溃事件

Linux:监听 SIGILL SIGSEGV 等异常信号 获取崩溃事件

工作原理示意图

2023-03-01-breakpad.png

图片右上角是一个完整的应用程序,它包含了三部分即程序代码、Breakpad Client(即 brekapad 提供出来的静态库),调式信息

在 Build System中 breakpad 的 symbol 生成工具借助应用层序中的 Debugging Information 这一部分生成一个 Google 自己的符号文件,最终在发布应用层序的时候使用 strip 将调式信息去除

在 User's System中运行的应用程序是通过 strip 去除了调式信息的,若应用程序发生 Crash,Breakpad client 就会写 minidump 文件到指定目录,也可以将产生的 minidump 文件发送到远端服务器即 Crash Colletcor。

在 Crash Collector就可以利用 Build System 中产生的 symol 文件和 User's System 中上报的 minidump 文件生成用户可读的 stack trace

3.2.3 使用示例 获取 breakpad 源码

github.com/google/brea…

执行安装 breakpad 1. cd breakpad 目录 2. 直接命令窗口输入: ./configure && make 移植 Breakpad 到客户端程序

breakpad 源码导入应用程序 cpp 目录下

2023-03-01-image -5--3.png

然后在 breakpad 中创建 CMakeLists.txt

cmake_minimum_required(VERSION 3.18.1)   #导入头文件 include_directories(src src/common/android/include) #支持汇编文件的编译 enable_language(ASM) #源文件编译为静态库 add_library(breakpad STATIC         src/client/linux/crash_generation/crash_generation_client.cc         src/client/linux/dump_writer_common/thread_info.cc         src/client/linux/dump_writer_common/ucontext_reader.cc         src/client/linux/handler/exception_handler.cc         src/client/linux/handler/minidump_descriptor.cc         src/client/linux/log/log.cc         src/client/linux/microdump_writer/microdump_writer.cc         src/client/linux/minidump_writer/linux_dumper.cc         src/client/linux/minidump_writer/linux_ptrace_dumper.cc         src/client/linux/minidump_writer/minidump_writer.cc         src/client/linux/minidump_writer/pe_file.cc         src/client/minidump_file_writer.cc         src/common/convert_UTF.cc         src/common/md5.cc         src/common/string_conversion.cc         src/common/linux/breakpad_getcontext.S         src/common/linux/elfutils.cc         src/common/linux/file_id.cc         src/common/linux/guid_creator.cc         src/common/linux/linux_libc_support.cc         src/common/linux/memory_mapped_file.cc         src/common/linux/safe_readlink.cc) #导入相关的库 target_link_libraries(breakpad log)

breakpad 中的 CMakeLists.txt 创建完成后,还需要在 cpp 目录下的 CMakeLists.txt 中进行配置,将刚刚创建的 CMakeLists.txt 引入进去

cmake_minimum_required(VERSION 3.18.1)   #引入头文件 include_directories(breakpad/src breakpad/src/common/android/include)   add_library(nativecrash SHARED nativecrashlib.cpp)   #添加子目录,会自动查找这个目录下的 CMakeList add_subdirectory(breakpad)   target_link_libraries(nativecrash log breakpad) breakpad 初始化

然后在自己项目的 native 文件中对 breakpad 进行初始化,如下

#include #include #include "breakpad/src/client/linux/handler/exception_handler.h" #include "breakpad/src/client/linux/handler/minidump_descriptor.h" /** * 引起 crash */ void Crash() { volatile int *a = (int *) (NULL); *a = 1; } extern "C" JNIEXPORT void JNICALL Java_com_elijah_nativedemo_MainActivity_nativeCrash(JNIEnv *env, jobject thiz) { Crash(); } //回调函数 bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { printf("Dump path: %s\n", descriptor.path()); return false; } //breakpad 初始化 extern "C" JNIEXPORT void JNICALL Java_com_elijah_nativedemo_MainActivity_initNative(JNIEnv *env, jclass clazz, jstring path_) { const char *path = env->GetStringUTFChars(path_, 0); google_breakpad::MinidumpDescriptor descriptor(path); static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1); env->ReleaseStringUTFChars(path_, path); } Java 层代码

Java 层传入 Crash dump 文件的保存路径,用于崩溃时文件的生成

package com.elijah.nativedemo; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.View; import java.io.File; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("nativedemo"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(this); findViewById(R.id.crash) .setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { nativeCrash(); } }); } public static void init(Context context){ Context applicationContext = context.getApplicationContext(); File file = new File(applicationContext.getExternalCacheDir(),"native_crash"); if(!file.exists()){ file.mkdirs(); } initNative(file.getAbsolutePath()); } /** * 模拟崩溃 */ public static native void nativeCrash(); /** * 初始化 breakpad * @param path */ private static native void initNative(String path); } 捕获 Crash,解析 dump

Native Crash 产生后,breakpad 会捕获 crash 信息,生成后缀为.dmp的 dump 文件到指定目录下。

2023-03-01-image -6--3.png

.dmp 格式的文件通常无法查看,需要解析工具对这个文件进行解析。解析工具在步骤“执行安装 breakpad”中就已经生成在 breakpad/src/processor目录下,名为 minidump_stackwalk。

输入如下指令即可解析 dump 文件

./minidump_stackwalk my.dump > crash.txt

生成的 crash.txt 如下图所示,关键代码是红框的部分,Thread 0 后面有一个 crashed 标识,说明这里是发生崩溃的线程,而下面就是崩溃的文件以及内存地址,使用 3.1 中介绍的 addr2line 工具进行解析即可得到问题方法与行号

2023-03-01-image -7--2.png

示例代码:github.com/elijah398/N…

参考文献

Android NativeCrash 捕获与解析

Android---Native层崩溃的监听工具BreakPad



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3